package com.github.sundeepk.compactcalendarview;


import com.github.sundeepk.compactcalendarview.domain.Event;
import com.github.sundeepk.compactcalendarview.lib.Scroller;
import ohos.agp.components.*;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.agp.utils.Rect;
import ohos.app.Context;
import ohos.multimodalinput.event.MmiPoint;
import ohos.multimodalinput.event.TouchEvent;

import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;

/**
 * 日历
 *
 * @since 2021/06/21
 */
public class CompactCalendarView extends Component implements Component.EstimateSizeListener,
        Component.DrawTask, Component.TouchEventListener {

    /**
     * FILL_LARGE_INDICATOR
     */
    public static final int FILL_LARGE_INDICATOR = 1;
    /**
     * NO_FILL_LARGE_INDICATOR
     */
    public static final int NO_FILL_LARGE_INDICATOR = 2;
    /**
     * SMALL_INDICATOR
     */
    public static final int SMALL_INDICATOR = 3;

    private final AnimationHandler animationHandler;
    private final CompactCalendarController compactCalendarController;
    private boolean horizontalScrollEnabled = true;
    private Point downPoint;


    /**
     * CompactCalendarViewListener
     */
    public interface CompactCalendarViewListener {
        void onDayClick(Date dateClicked);

        void onMonthScroll(Date firstDayOfNewMonth);
    }

    /**
     * CompactCalendarAnimationListener
     */
    public interface CompactCalendarAnimationListener {
        void onOpened();

        void onClosed();
    }


    public CompactCalendarView(Context context) {
        this(context, null);
    }

    public CompactCalendarView(Context context, AttrSet attrs) {
        this(context, attrs, 0);
    }

    public CompactCalendarView(Context context, AttrSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        Scroller scrollHelper = new Scroller(context);
        compactCalendarController = new CompactCalendarController(new Paint(), scrollHelper,
                new Rect(), attrs, getContext(), Color.argb(255, 233, 84, 81),
                Color.argb(255, 64, 64, 64),
                Color.argb(255, 219, 219, 219), VelocityDetector.obtainInstance(),
                Color.argb(255, 100, 68, 65), new EventsContainer(Calendar.getInstance()),
                Locale.getDefault(), TimeZone.getDefault());
        DraggedListener gestureListener = new DraggedListener() {
            @Override
            public void onDragDown(Component component, DragInfo dragInfo) {
                downPoint = dragInfo.downPoint;
            }

            @Override
            public void onDragStart(Component component, DragInfo dragInfo) {
            }

            @Override
            public void onDragUpdate(Component component, DragInfo dragInfo) {
                if (horizontalScrollEnabled) {
                    compactCalendarController.onScroll((float) dragInfo.xOffset, (float) dragInfo.yOffset);
                    invalidate();
                }
            }

            @Override
            public void onDragEnd(Component component, DragInfo dragInfo) {
                mContext.getUITaskDispatcher().asyncDispatch(new Runnable() {
                    @Override
                    public void run() {
                        if (compactCalendarController.computeScroll()) {
                            invalidate();
                            mContext.getUITaskDispatcher().delayDispatch(this, 10);
                        }
                    }
                });
            }

            @Override
            public void onDragCancel(Component component, DragInfo dragInfo) {
            }
        };
        setDraggedListener(DRAG_HORIZONTAL, gestureListener);
        animationHandler = new AnimationHandler(compactCalendarController, this);
        setEstimateSizeListener(this);
        addDrawTask(this);
        setTouchEventListener(this);
    }


    /**
     * setAnimationListener
     *
     * @param compactCalendarAnimationListener compactCalendarAnimationListener
     */
    public void setAnimationListener(CompactCalendarAnimationListener compactCalendarAnimationListener) {
        animationHandler.setCompactCalendarAnimationListener(compactCalendarAnimationListener);
    }

    /**
     * Use a custom locale for compact calendar and reinitialise the view.
     *
     * @param timeZone timeZone
     * @param locale   locale
     */
    public void setLocale(TimeZone timeZone, Locale locale) {
        compactCalendarController.setLocale(timeZone, locale);
        invalidate();
    }

    /**
     * Compact calendar will use the locale to determine the abbreviation to use as the day column names.
     * The default is to use the default locale and to abbreviate the day names to one character.
     * Setting this to true will displace the short weekday string provided by java.
     *
     * @param useThreeLetterAbbreviation useThreeLetterAbbreviation
     */
    public void setUseThreeLetterAbbreviation(boolean useThreeLetterAbbreviation) {
        compactCalendarController.setUseWeekDayAbbreviation(useThreeLetterAbbreviation);
        invalidate();
    }

    /**
     * setCalendarBackgroundColor
     *
     * @param calenderBackgroundColor calenderBackgroundColor
     */
    public void setCalendarBackgroundColor(final int calenderBackgroundColor) {
        compactCalendarController.setCalenderBackgroundColor(calenderBackgroundColor);
        invalidate();
    }

    /**
     * Sets the name for each day of the week. No attempt is made to
     * adjust width or text size based on the length of each day name.
     * Works best with 3-4 characters for each day.
     *
     * @param dayColumnNames dayColumnNames
     */
    public void setDayColumnNames(String[] dayColumnNames) {
        compactCalendarController.setDayColumnNames(dayColumnNames);
    }

    /**
     * setFirstDayOfWeek
     *
     * @param dayOfWeek dayOfWeek
     */
    public void setFirstDayOfWeek(int dayOfWeek) {
        compactCalendarController.setFirstDayOfWeek(dayOfWeek);
        invalidate();
    }

    /**
     * setCurrentSelectedDayBackgroundColor
     *
     * @param currentSelectedDayBackgroundColor currentSelectedDayBackgroundColor
     */
    public void setCurrentSelectedDayBackgroundColor(int currentSelectedDayBackgroundColor) {
        compactCalendarController.setCurrentSelectedDayBackgroundColor(currentSelectedDayBackgroundColor);
        invalidate();
    }

    /**
     * setCurrentDayBackgroundColor
     *
     * @param currentDayBackgroundColor currentDayBackgroundColor
     */
    public void setCurrentDayBackgroundColor(int currentDayBackgroundColor) {
        compactCalendarController.setCurrentDayBackgroundColor(currentDayBackgroundColor);
        invalidate();
    }

    /**
     * getHeightPerDay
     *
     * @return int
     */
    public int getHeightPerDay() {
        return compactCalendarController.getHeightPerDay();
    }

    /**
     * setListener
     *
     * @param listener listener
     */
    public void setListener(CompactCalendarViewListener listener) {
        compactCalendarController.setListener(listener);
    }

    /**
     * getFirstDayOfCurrentMonth
     *
     * @return Date
     */
    public Date getFirstDayOfCurrentMonth() {
        return compactCalendarController.getFirstDayOfCurrentMonth();
    }

    /**
     * shouldDrawIndicatorsBelowSelectedDays
     *
     * @param shouldDrawIndicatorsBelowSelectedDays shouldDrawIndicatorsBelowSelectedDays
     */
    public void shouldDrawIndicatorsBelowSelectedDays(boolean shouldDrawIndicatorsBelowSelectedDays) {
        compactCalendarController.shouldDrawIndicatorsBelowSelectedDays(shouldDrawIndicatorsBelowSelectedDays);
    }

    /**
     * setCurrentDate
     *
     * @param dateTimeMonth dateTimeMonth
     */
    public void setCurrentDate(Date dateTimeMonth) {
        compactCalendarController.setCurrentDate(dateTimeMonth);
        invalidate();
    }

    /**
     * getWeekNumberForCurrentMonth
     *
     * @return int
     */
    public int getWeekNumberForCurrentMonth() {
        return compactCalendarController.getWeekNumberForCurrentMonth();
    }

    /**
     * setShouldDrawDaysHeader
     *
     * @param shouldDrawDaysHeader shouldDrawDaysHeader
     */
    public void setShouldDrawDaysHeader(boolean shouldDrawDaysHeader) {
        compactCalendarController.setShouldDrawDaysHeader(shouldDrawDaysHeader);
    }

    /**
     * setCurrentSelectedDayTextColor
     *
     * @param currentSelectedDayTextColor currentSelectedDayTextColor
     */
    public void setCurrentSelectedDayTextColor(int currentSelectedDayTextColor) {
        compactCalendarController.setCurrentSelectedDayTextColor(currentSelectedDayTextColor);
    }

    /**
     * setCurrentDayTextColor
     *
     * @param currentDayTextColor currentDayTextColor
     */
    public void setCurrentDayTextColor(int currentDayTextColor) {
        compactCalendarController.setCurrentDayTextColor(currentDayTextColor);
    }

    /**
     * see {@link #addEvent(Event, boolean)} when adding single events to control if calendar should redraw
     * or {@link #addEvents(List)}  when adding multiple events
     *
     * @param event 事件
     */
    public void addEvent(Event event) {
        addEvent(event, true);
    }

    /**
     * Adds an event to be drawn as an indicator in the calendar.
     * If adding multiple events see {@link #addEvents(List)}} method.
     *
     * @param event            to be added to the calendar
     * @param shouldInvalidate true if the view should invalidate
     */
    public void addEvent(Event event, boolean shouldInvalidate) {
        compactCalendarController.addEvent(event);
        if (shouldInvalidate) {
            invalidate();
        }
    }

    /**
     * Adds multiple events to the calendar and invalidates the view once all events are added.
     *
     * @param events 事件集合
     */
    public void addEvents(List<Event> events) {
        compactCalendarController.addEvents(events);
        invalidate();
    }

    /**
     * Fetches the events for the date passed in
     *
     * @param date 日期
     * @return 事件集合
     */
    public List<Event> getEvents(Date date) {
        return compactCalendarController.getCalendarEventsFor(date.getTime());
    }

    /**
     * Fetches the events for the epochMillis passed in
     *
     * @param epochMillis 时间
     * @return 事件集合
     */
    public List<Event> getEvents(long epochMillis) {
        return compactCalendarController.getCalendarEventsFor(epochMillis);
    }

    /**
     * Fetches the events for the month of the epochMillis passed in and returns a sorted list of events
     *
     * @param epochMillis 时间
     * @return 事件集合
     */
    public List<Event> getEventsForMonth(long epochMillis) {
        return compactCalendarController.getCalendarEventsForMonth(epochMillis);
    }

    /**
     * Fetches the events for the month of the date passed in and returns a sorted list of events
     *
     * @param date 日期
     * @return 事件集合
     */
    public List<Event> getEventsForMonth(Date date) {
        return compactCalendarController.getCalendarEventsForMonth(date.getTime());
    }

    /**
     * Remove the event associated with the Date passed in
     *
     * @param date 日期
     */
    public void removeEvents(Date date) {
        compactCalendarController.removeEventsFor(date.getTime());
    }

    /**
     * removeEvents
     *
     * @param epochMillis epochMillis
     */
    public void removeEvents(long epochMillis) {
        compactCalendarController.removeEventsFor(epochMillis);
    }

    /**
     * see {@link #removeEvent(Event, boolean)} when removing single events to control if calendar should redraw
     * or {@link #removeEvents(List)} (java.util.List)}  when removing multiple events
     *
     * @param event 事件
     */
    public void removeEvent(Event event) {
        removeEvent(event, true);
    }

    /**
     * Removes an event from the calendar.
     * If removing multiple events see {@link #removeEvents(List)}
     *
     * @param event            event to remove from the calendar
     * @param shouldInvalidate true if the view should invalidate
     */
    public void removeEvent(Event event, boolean shouldInvalidate) {
        compactCalendarController.removeEvent(event);
        if (shouldInvalidate) {
            invalidate();
        }
    }

    /**
     * Removes multiple events from the calendar and invalidates the view once all events are added.
     *
     * @param events 事件集
     */
    public void removeEvents(List<Event> events) {
        compactCalendarController.removeEvents(events);
        invalidate();
    }

    /**
     * Clears all Events from the calendar.
     */
    public void removeAllEvents() {
        compactCalendarController.removeAllEvents();
        invalidate();
    }

    /**
     * setIsRtl
     *
     * @param isRtl isRtl
     */
    public void setIsRtl(boolean isRtl) {
        compactCalendarController.setIsRtl(isRtl);
    }

    /**
     * shouldSelectFirstDayOfMonthOnScroll
     *
     * @param shouldSelectFirstDayOfMonthOnScroll shouldSelectFirstDayOfMonthOnScroll
     */
    public void shouldSelectFirstDayOfMonthOnScroll(boolean shouldSelectFirstDayOfMonthOnScroll) {
        compactCalendarController.setShouldSelectFirstDayOfMonthOnScroll(shouldSelectFirstDayOfMonthOnScroll);
    }

    /**
     * currentSelectedDayIndicatorStyle
     *
     * @param currentSelectedDayIndicatorStyle currentSelectedDayIndicatorStyle
     */
    public void setCurrentSelectedDayIndicatorStyle(final int currentSelectedDayIndicatorStyle) {
        compactCalendarController.setCurrentSelectedDayIndicatorStyle(currentSelectedDayIndicatorStyle);
        invalidate();
    }

    /**
     * currentDayIndicatorStyle
     *
     * @param currentDayIndicatorStyle currentDayIndicatorStyle
     */
    public void setCurrentDayIndicatorStyle(final int currentDayIndicatorStyle) {
        compactCalendarController.setCurrentDayIndicatorStyle(currentDayIndicatorStyle);
        invalidate();
    }

    /**
     * eventIndicatorStyle
     *
     * @param eventIndicatorStyle eventIndicatorStyle
     */
    public void setEventIndicatorStyle(final int eventIndicatorStyle) {
        compactCalendarController.setEventIndicatorStyle(eventIndicatorStyle);
        invalidate();
    }

    private void checkTargetHeight() {
        if (compactCalendarController.getTargetHeight() <= 0) {
            throw new IllegalStateException("Target height must be set in xml properties " +
                    "in order to expand/collapse CompactCalendar.");
        }
    }

    /**
     * displayOtherMonthDays
     *
     * @param displayOtherMonthDays displayOtherMonthDays
     */
    public void displayOtherMonthDays(boolean displayOtherMonthDays) {
        compactCalendarController.setDisplayOtherMonthDays(displayOtherMonthDays);
        invalidate();
    }

    /**
     * targetHeight
     *
     * @param targetHeight targetHeight
     */
    public void setTargetHeight(int targetHeight) {
        compactCalendarController.setTargetHeight(targetHeight);
        checkTargetHeight();
    }

    /**
     * showCalendar
     */
    public void showCalendar() {
        checkTargetHeight();
        animationHandler.openCalendar();
    }

    /**
     * hideCalendar
     */
    public void hideCalendar() {
        checkTargetHeight();
        animationHandler.closeCalendar();
    }

    /**
     * showCalendarWithAnimation
     */
    public void showCalendarWithAnimation() {
        checkTargetHeight();
        animationHandler.openCalendarWithAnimation();
    }

    /**
     * hideCalendarWithAnimation
     */
    public void hideCalendarWithAnimation() {
        checkTargetHeight();
        animationHandler.closeCalendarWithAnimation();
    }

    /**
     * Moves the calendar to the right. This will show the next month when {@link #setIsRtl(boolean)}
     * is set to false. If in rtl mode, it will show the previous month.
     */
    public void scrollRight() {
        compactCalendarController.scrollRight();
        invalidate();
    }

    /**
     * Moves the calendar to the left. This will show the previous month when {@link #setIsRtl(boolean)}
     * is set to false. If in rtl mode, it will show the next month.
     */
    public void scrollLeft() {
        compactCalendarController.scrollLeft();
        invalidate();
    }

    /**
     * isAnimating
     *
     * @return boolean
     */
    public boolean isAnimating() {
        return animationHandler.isAnimating();
    }

    @Override
    public boolean onEstimateSize(int parentWidth, int parentHeight) {
        int width = EstimateSpec.getSize(parentWidth);
        int height = EstimateSpec.getSize(parentHeight);
        if (width > 0 && height > 0) {
            compactCalendarController.onMeasure(width, height, getPaddingRight(), getPaddingLeft());
        }
        setEstimatedSize(width, height);
        return false;
    }


    @Override
    public void onDraw(Component component, Canvas canvas) {
        compactCalendarController.onDraw(canvas);
    }


    /**
     * enableHorizontalScroll
     *
     * @param enableHorizontalScroll enableHorizontalScroll
     */
    public void shouldScrollMonth(boolean enableHorizontalScroll) {
        this.horizontalScrollEnabled = enableHorizontalScroll;
    }


    @Override
    public boolean onTouchEvent(Component component, TouchEvent event) {
        if (horizontalScrollEnabled) {
            compactCalendarController.onTouch(event);
            invalidate();
        }
        int index = event.getIndex();
        MmiPoint pointerPosition = event.getPointerPosition(index);
        if (event.getAction() == TouchEvent.PRIMARY_POINT_UP && downPoint.getPointX() == pointerPosition.getX()) {
            compactCalendarController.onSingleTapUp(downPoint);
            invalidate();
        }

        return true;
    }

}
